package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

func Trans(key, def string) string {
	dict := map[string]string{
		"page":              "页码",
		"access_token":      "认证信息",
		"os":                "系统类型，android、ios",
		"language":          "语言：en_us、zh_cn",
		"main_network":      "网络：main_network、testnet",
		"wallet_addr":       "钱包地址",
		"coin_symbol":       "币种",
		"userInfo":          "用户信息",
		"balance":           "余额",
		"kline":             "K线",
		"market":            "行情",
		"create":            "创建",
		"delete":            "删除",
		"update":            "更新",
		"index":             "索引",
		"bank":              "银行",
		"certification":     "认证",
		"history":           "历史",
		"generateAddress":   "生成地址",
		"member":            "会员",
		"addContact":        "添加合约",
		"bindMobileVarcode": "个人中心绑定手机号",
		"infoUpdate":        "个人中心资料修改",
		"changePassword":    "修改交易所密码",
		"feedback":          "反馈",
		"file":              "文件",
		"chat":              "聊天",
		"currency":          "货币",
		"bargain":           "交易",
		"gathering":         "归集",
		"init":              "初始",
		"merchants":         "商户",
		"exchange":          "兑换",
		"notify":            "通知",
		"register":          "注册",
		"otc":               "OTC(场外交易)",
		"wallet":            "钱包",
		"forgetPassword":    "忘记密码",
		"generateAddr":      "生成货币地址",
		"vote":              "投票",
		"withdraw":          "提现",
		"user":              "用户",
		"bindEmail":         "绑定邮箱",
		"robot":             "机器人",
		"addAddress":        "添加地址",
		"delAddress":        "删除地址",
		"editAddress":       "编辑地址",
		"getAddress":        "获取地址",
		"coinList":          "币种列表",
		"bindPhone":         "绑定手机",
		"uploadImage":       "上传图像",
		"password":          "密码",
		"email":             "邮箱",
		"orderLimit":        "交易所限价单买卖",
		"cancelOrder":       "撤销委托单",
		"bankInfo":          "银行信息",
		"wallet_memo":       "钱包备注",
		"memo":              "备注",
		"tx_id":             "交易ID",
		"add":               "添加",
		"amount":            "数量",
		"getRecord":         "获取记录",
		"getVersion":        "获取版本",
		"record":            "记录",
		"transactionDetail": "交易详情",
		"contactList":       "联系人列表",
		"deleteContact":     "删除联系人",
		"getMobileVarcode":  "个人中心验证手机号",
		"info":              "信息",
		"modifyContact":     "修改联系人资料",
		"messageList":       "消息列表",
		"checkGoogleCode":   "谷歌验证码",
		"closeGoogleCheck":  "关闭谷歌验证",
		"applyLog":          "获取用户提现申请记录",
		"rechargeDetails":   "银行交易明细",
		"duihuan":           "兑换",
		"getInfo":           "获取信息",
		"trade":             "交易",
		"tasklock":          "任务锁",
		"release":           "释放",
	}
	if dict[key] != "" {
		key = dict[key]
	} else if def != "" {
		key = def
	}
	return key
}

func FirstUpper(s string) string {
	if s == "" {
		return ""
	}
	return strings.ToUpper(s[:1]) + s[1:]
}

func FirstLower(s string) string {
	if s == "" {
		return ""
	}
	return strings.ToLower(s[:1]) + s[1:]
}

func GetAccessToken() {
	CallApi("/api/register/sign")
}

func main() {
	files, _ := WalkDir("./src/controllers", "")
	for _, fn := range files {
		fmt.Println(fn)
		ReadFile(fn)
	}
	//ReadFile("src\\controllers\\AdminController.php")
	//CallApi("/api/register/sign")
}

type Action struct {
	Name     string
	Url      string
	Method   string
	Params   []string
	VarName  string
	RpcNames []string
	Remarks  string
}

type Ctrl struct {
	Filename  string
	ClassName string
	PrevLine  string
	Actions   []Action
}

func (ctrl *Ctrl) AddParamForAction(idx int, key string) {
	params := ctrl.Actions[idx].Params
	hasKey := false
	for _, paramKey := range params {
		if paramKey == key {
			hasKey = true
		}
	}
	if !hasKey {
		ctrl.Actions[idx].Params = append(params, key)
	}
}

func (ctrl *Ctrl) AddRpcForAction(idx int, key string) {
	rpcNames := ctrl.Actions[idx].RpcNames
	hasKey := false
	for _, rpcKey := range rpcNames {
		if rpcKey == key {
			hasKey = true
		}
	}
	if !hasKey {
		ctrl.Actions[idx].RpcNames = append(rpcNames, key)
	}
}

func SnakeString(s string) string {
	data := make([]byte, 0, len(s)*2)
	j := false
	num := len(s)
	for i := 0; i < num; i++ {
		d := s[i]
		// or通过ASCII码进行大小写的转化
		// 65-90（A-Z），97-122（a-z）
		//判断如果字母为大写的A-Z就在前面拼接一个_
		if i > 0 && d >= 'A' && d <= 'Z' && j {
			data = append(data, '-')
		}
		if d != '_' {
			j = true
		}
		data = append(data, d)
	}
	//ToLower把大写字母统一转小写
	return strings.ToLower(string(data[:]))
}

func CallApi(uri string) (body []byte, err error) {
	url := "http://192.168.1.108" + uri
	client := &http.Client{}
	reqBody := strings.NewReader("mobile_phone=8615998887095&password=123456abc")
	req, _ := http.NewRequest("POST", url, reqBody)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	return
}

func ParseResponseJSON(data []byte) (joData map[string]interface{}, err error) {
	joData = make(map[string]interface{})
	err = json.Unmarshal(data, &joData)
	return
}

func ParseRemark(lineStr string) (remarks string) {
	c := regexp.MustCompile(`// *([^\n]+)`)
	matched := c.FindAllStringSubmatch(lineStr, -1)
	if len(matched) > 0 {
		remarks = matched[0][1]
	}
	return
}

func ParseRpc(lineStr string) (param string) {
	c := regexp.MustCompile(`do_rpc\(['"](.*?)['"]`)
	matched := c.FindAllStringSubmatch(lineStr, -1)
	if len(matched) > 0 {
		param = matched[0][1]
	}
	return
}

func ParseRpc2(lineStr string) (param string) {
	c := regexp.MustCompile(`do_rpc\((\$[a-zA-Z0-9_]+)`)
	matched := c.FindAllStringSubmatch(lineStr, -1)
	if len(matched) > 0 {
		param = matched[0][1]
	}
	return
}

func ParseParam(lineStr string) (method, param string) {
	c := regexp.MustCompile(`->(post|get)\(['"](.*?)['"]`)
	matched := c.FindAllStringSubmatch(lineStr, -1)
	if len(matched) > 0 {
		method = matched[0][1]
		param = matched[0][2]
	}
	return
}

func ParseParam2(varName, lineStr string) (param string) {
	ptn := `\` + varName + `\[['"](.*?)['"]\]`
	c := regexp.MustCompile(ptn)
	matched := c.FindAllStringSubmatch(lineStr, -1)
	if len(matched) > 0 {
		param = matched[0][1]
	}
	return
}

func ParseVarName(lineStr string) (varName string) {
	ptn := regexp.MustCompile("(\\$.*?) *=.*?request\\->post\\(\\)")
	matched := ptn.FindAllStringSubmatch(lineStr, -1)
	if len(matched) > 0 {
		varName = matched[0][1]
	}
	return
}

func ParseClassName(lineStr string) string {
	c := regexp.MustCompile(`class (.*?)Controller`)
	matched := c.FindAllStringSubmatch(lineStr, -1)
	className := ""
	if len(matched) > 0 {
		className = matched[0][1]
	}
	return className
}

func ParseAction(lineStr string) string {
	c := regexp.MustCompile(`public +function +action([0-9a-zA-Z\_\-]+)`)
	matched := c.FindAllStringSubmatch(lineStr, -1)
	action := ""
	if len(matched) > 0 {
		action = matched[0][1]
	}
	return action
}

func ReadFile(filename string) {
	fd, err := os.Open(filename)
	var ctrl = Ctrl{
		Filename: filename,
	}
	defer fd.Close()
	if err != nil {
		fmt.Println("read error:", err)
	}
	buff := bufio.NewReader(fd)
	parsedClass := false
	for {
		data, _, eof := buff.ReadLine()
		if eof == io.EOF {
			break
		}
		lineStr := string(data)
		if parsedClass == false {
			className := ParseClassName(lineStr)
			if className != "" {
				parsedClass = true
				ctrl.ClassName = className
			}
		} else {
			actionName := ParseAction(lineStr)
			if actionName != "" {
				if actionName != "s" {
					ctrl.Actions = append(ctrl.Actions, Action{Name: actionName})
					remarks := ParseRemark(ctrl.PrevLine)
					if remarks != "" {
						actionNum := len(ctrl.Actions)
						idx := actionNum - 1
						ctrl.Actions[idx].Remarks = remarks
					}
				}
			} else {
				actionNum := len(ctrl.Actions)
				if actionNum > 0 {
					idx := actionNum - 1
					method, paramName := ParseParam(lineStr)
					if paramName != "" {
						action := ctrl.Actions[idx]
						if action.Method == "" && method != "" {
							action.Method = method
						}
						//ctrl.Actions[idx].Params = append(action.Params, paramName)
						ctrl.AddParamForAction(idx, paramName)
					} else {
						varName := ParseVarName(lineStr)
						if varName != "" {
							ctrl.Actions[idx].VarName = varName
						} else if ctrl.Actions[idx].VarName != "" {
							paramName2 := ParseParam2(ctrl.Actions[idx].VarName, lineStr)

							if paramName2 != "" {
								//action := ctrl.Actions[idx]
								//ctrl.Actions[idx].Params = append(action.Params, paramName2)
								ctrl.AddParamForAction(idx, paramName2)
							}
						} else {
							rpcParamName := ParseRpc(lineStr)
							if rpcParamName != "" {
								ctrl.AddRpcForAction(idx, rpcParamName)
							} else {
								rpcVarName := ParseRpc2(lineStr)
								if rpcVarName != "" {
									ctrl.AddRpcForAction(idx, rpcVarName)
								}
							}
						}
					}
				}
			}
		}
		ctrl.PrevLine = lineStr
	}
	if ctrl.ClassName != "" {
		fmt.Println(ctrl.Actions)
		WriteCtrl(ctrl)
	}
}

func WriteCtrl(ctrl Ctrl) {
	filePath := fmt.Sprintf("./src/raw/%s.php", ctrl.ClassName)
	os.Remove(filePath)
	file, err := os.OpenFile(filePath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0755)
	if err != nil {
		fmt.Println("文件打开失败", err)
	}
	//及时关闭file句柄
	defer file.Close()
	//写入文件时，使用带缓存的 *Writer
	write := bufio.NewWriter(file)
	clsNameLower := strings.ToLower(ctrl.ClassName)
	content := "/**\n"
	content += fmt.Sprintf(" * @apiDefine %s %s \n", clsNameLower, Trans(clsNameLower, ""))
	content += " */\n\n\n"

	actionNum := len(ctrl.Actions)

	for i := 0; i < actionNum; i++ {
		action := ctrl.Actions[i]
		met := action.Method
		if met == "" {
			met = "post"
		}
		actionNameFirstLower := FirstLower(action.Name)
		actionNameFirstUpper := FirstUpper(action.Name)
		actionNameSnakeMode := SnakeString(action.Name)
		action.Url = fmt.Sprintf("/api/%s/%s", clsNameLower, actionNameSnakeMode)
		content += "    /**\n"
		content += fmt.Sprintf("     * @api {%s} %s %s\n", met, action.Url, Trans(actionNameFirstLower, action.Remarks))
		content += "     * @apiDescription \n"
		if action.Remarks != "" {
			content += "     * <p>" + action.Remarks + "</p>\n"
		}
		content += "     * <p style='font-weight:bold;'>" + strings.Replace(ctrl.Filename, "src\\", "路径：api\\", 1) + " -> action" + action.Name + "</p>\n"
		for _, rpcName := range action.RpcNames {
			content += "     * <p style='color:red;font-weight:bold;'>rpc -> " + rpcName + "</p>\n"
		}
		content += fmt.Sprintf("     * @apiName %s%s\n", clsNameLower, actionNameFirstUpper)
		content += fmt.Sprintf("     * @apiGroup %s\n", clsNameLower)
		content += "     *\n"
		paramNum := len(action.Params)
		for j := 0; j < paramNum; j++ {
			param := action.Params[j]
			content += fmt.Sprintf("     * @apiParam {String} %s %s\n", param, Trans(param, ""))
		}
		content += "     * @apiSuccessExample {json} Success-Response:\n"
		content += "     *     HTTP/1.1 200 OK\n"
		content += "     */\n\n\n"
	}

	write.WriteString(content)
	//Flush将缓存的文件真正写入到文件中
	write.Flush()
}

//获取指定目录及所有子目录下的所有文件，可以匹配后缀过滤。
func WalkDir(dirPth, suffix string) (files []string, err error) {
	files = make([]string, 0, 30)
	suffix = strings.ToUpper(suffix) //忽略后缀匹配的大小写
	err = filepath.Walk(dirPth, func(filename string, fi os.FileInfo, err error) error { //遍历目录
		if fi.IsDir() {                                                                  // 忽略目录
			return nil
		}
		if strings.HasSuffix(strings.ToUpper(fi.Name()), suffix) {
			files = append(files, filename)
		}
		return nil
	})
	return files, err
}
